package controller;

import java.math.BigDecimal;
import java.util.Date;
import java.util.Iterator;
import java.util.TreeSet;

import model.Account;
import model.AccountActivity;
import model.AccountException;

/**
 * Gerenciador de contas banc�rias
 * @author Matheus
 * @author Tiago Lucas
 *
 */
public class AccountManager {

	private TreeSet<Account> accounts;
	private Account defaultAccount;

	/**
	 * Consrutor
	 */
	public AccountManager() {
		this.accounts = new TreeSet<Account>();
		this.defaultAccount = new Account();
	}

	/**
	 * Adiciona opera��o na conta padr�o, com tipo, m�todo e quantia. Retorna ID �nico.
	 * @param type tipo da opera��o
	 * @param way m�todo da opera��o
	 * @param amount quantia da opera��o
	 * @return Identificador �nico
	 */
	public String addOperationIntoDefaultAccount(String type, String way, BigDecimal amount, String description, Date date) throws AccountException {
		
		// Se o valor for negativo, abortar
		if (amount.intValue() < 0 ) {
			throw new AccountException("Business validation: negative amount");
		}
		
		// Se o valor for zero, abortar
		if (amount.intValue() == 0 ) {
			throw new AccountException("Business validation: zero amount");
		}
		
		// Cria uma opera��o		
		AccountActivity accountactivity = new AccountActivity(type,way,amount,description, date);
		
		// Adicionando � lista de opera��es da conta
		defaultAccount.addAccountActivity(accountactivity);
		
		return accountactivity.getId();
	}
	
	
	public TreeSet<Account> getAccounts(){
		return accounts;
	}
	/**
	 * Adiciona opera��o em conta espec�fica, com tipo, m�todo e quantia. Retorna ID �nico.
	 * @param type tipo da opera��o
	 * @param way m�todo da opera��o
	 * @param amount quantia da opera��o
	 * @return Identificador �nico
	 * @throws AccountException
	 */
	public String addOperation(String accId, String type, String way, BigDecimal amount, String description, Date date) throws AccountException {
		
		// Recuperar o Objeto conta dono do accId
		Account account = findAccount(accId);
		
		if (account == null) {
			throw new AccountException("Invalid Account");
		}
		
		// Se o valor for negativo, abortar
		if (amount.intValue() < 0 ) {
			throw new AccountException("Business validation: negative amount");
		}
		
		// Se o valor for zero, abortar
		if (amount.intValue() == 0 ) {
			throw new AccountException("Business validation: zero amount");
		}
		
		AccountActivity accountactivity = new AccountActivity(type,way,amount,description, date);
		
		account.addAccountActivity(accountactivity);
		
		return accountactivity.getId();
	}
	
	/**
	 * Remove opera��o por Id
	 * @param opId identificador da opera��o
	 * @return True se a remo��o for bem sucedida, false caso contr�rio
	 * @throws AccountException
	 */
	public boolean removeOperation(String opId) throws AccountException {
		
		AccountActivity acct = findAccountAcctivity(opId);
		
		// ter certeza que a opera��o existe
		if (acct == null) {
			throw new AccountException("Unknown operation");
		}
		
		Account acc = findAccountAcctivityAccount(opId);
		
		return acc.removeOperation(acct);
	}
	
	/**
	 * Retorna saldo da conta padr�o
	 * @return Saldo da conta padr�o
	 */
	public double getDefAccOverallAmount() {

		return defaultAccount.getBalance();
	}
	
	/**
	 * Retorna saldo de conta espec�fica
	 * @param accId identificador da conta
	 * @return double saldo da conta
	 * @throws AccountException
	 */
	public double getAccOverallAmount(String accId) throws AccountException {
		Account acc = findAccount(accId);
		
		if (acc == null) {
			throw new AccountException("Invalid Account");
		}
		
		return acc.getBalance();
	}
	
	/**
	 * Retorna conta referente ao identificador passado
	 * @param accId identificador da conta
	 * @return Account
	 */
	public Account findAccount(String accId) {

		Account acc = null;
		
		if (defaultAccount.getId().equals(accId)) {
			return defaultAccount;
		}
		
		
		Iterator<Account> itAcc = accounts.iterator();
		
		while (itAcc.hasNext()) {
			acc = itAcc.next();
			if (acc.getId().equals(accId)) {
				return acc;
			}
		}
		return null;
	}

	/**
	 * Testa se conta existe
	 * @param label r�tulo da conta
	 * @param agency ag�ncia da conta
	 * @param account n�mero da conta
	 * @return True se existir, false caso contr�rio
	 */
	public boolean accountExists(String label, String agency, String account) {
		Iterator<Account> itAcc = accounts.iterator();

		while (itAcc.hasNext()) {
			Account acc = itAcc.next();
			if ((acc.getLabel().compareTo(label) == 0) && 
				(acc.getAgency().equalsIgnoreCase(agency)) &&
				(acc.getAccount().equalsIgnoreCase(account))) {
				return true;
			}
		}
		
		return false;
	}
	
	/**
	 * Cria conta espec�fica, com R�tulo, ag�ncia e conta. Retorna Identificador �nico.
	 * @param label r�tulo da conta
	 * @param agency ag�ncia da conta
	 * @param account n�mero da conta
	 * @return Identificador �nico
	 */
	public String createAccount(String label, String agency, String account) {
		
		Account newaccount = new Account(label,agency,account);
		
		accounts.add(newaccount);
		return newaccount.getId().toString();
		
	}
	
	/**
	 * Remove conta espec�fica
	 * @param accId identificador da conta
	 * @return True se remover com sucesso, false caso contr�rio.
	 * @throws AccountException
	 */
	public boolean removeAccount(String accId) throws AccountException {
		
		Account acc = findAccount(accId);
		
		if (acc == null) {
			throw new AccountException("Account not found");
		}
		
		return accounts.remove(acc);
	}
	
	/**
	 * Remove uma conta
	 * @param account conta a ser removida
	 */
	public void removeAccount(Account account){
		accounts.remove(account);
	}
	
	/**
	 * Retorna tipo da opera��o
	 * @param opId identificador da opera��o
	 * @return Tipo da opera��o
	 * @throws AccountException
	 */
	public String getOperationType(String opId) throws AccountException {
		
		AccountActivity acct = findAccountAcctivity(opId);
		
		if (acct != null) {
			return acct.getType();
		} else {
			throw new AccountException("Unknown operation");
		}
	}
	
	/**
	 * Retorna m�todo da opera��o
	 * @param opId identificador da opera��o
	 * @return M�todo da opera��o
	 * @throws AccountException
	 */
	public String getOperationWay(String opId) throws AccountException {
		AccountActivity acct = findAccountAcctivity(opId);
		
		if (acct != null) {
			return acct.getWay();
		} else {
			throw new AccountException("Unknown operation");
		}
	}
	
	/**
	 * Retorna quantia da opera��o
	 * @param opId identificador da opera��o
	 * @return Quantia da opera��o
	 * @throws AccountException
	 */
	public BigDecimal getOperationAmount(String opId) throws AccountException {
		AccountActivity acct = findAccountAcctivity(opId);
		
		if (acct != null) {
			return acct.getValue();
		} else {
			throw new AccountException("Unknown operation");
		}
	}
	
	/**
	 * Retorna conta padr�o
	 * @param login login do usu�rio
	 * @return Conta padr�o
	 */
	private Account getDefaultAccount(String login) {

		return defaultAccount;
	}
	
	/**
	 * Retorna opera��o
	 * @param opId identificador da opera��o
	 * @return Opera��o
	 */
	private AccountActivity findAccountAcctivity(String opId) {

		if (defaultAccount.getAccountActivity(opId) != null) {
			return defaultAccount.getAccountActivity(opId);
		}
		
		Iterator<Account> itAcc = accounts.iterator();

		while (itAcc.hasNext()) {
			Account acc = itAcc.next();
			AccountActivity acct = acc.getAccountActivity(opId);
			if (acct != null) {
				return acct;
			}
		}
		
		return null;
	}
	
	/**
	 * Retorna conta referente a uma opera��o 
	 * @param opId identificador da opera��o
	 * @return Conta referente � opera��o
	 */
	private Account findAccountAcctivityAccount(String opId) {
		if (defaultAccount.getAccountActivity(opId) != null) {
			return defaultAccount;
		}
		
		
		Iterator<Account> itAcc = accounts.iterator();

		while (itAcc.hasNext()) {
			Account acc = itAcc.next();
			AccountActivity acct = acc.getAccountActivity(opId);
			if (acct != null) {
				return acc;
			}
		}
		
		return null;
	}

	/**
	 * Retorna o numero de operacoes em todas as contas gerenciadas aqui
	 * @return Quantidade de opera��es
	 */
	public int getNumberOfOperationsReport() {
		int result = 0;
		
		Iterator<Account> itAcc = accounts.iterator();
		
		while (itAcc.hasNext()) {
			result += itAcc.next().getNumberOfOperations();
		}
		
		result += defaultAccount.getNumberOfOperations();
		
		return result;
	}

	/**
	 * Retorna o n�mero de opera��es em uma conta
	 * @param accId identificador da conta
	 * @return Quantidade de opera��es
	 */
	public int getNumberOfOperationsReport(String accId) {
		Account acc = this.findAccount(accId);
		
		return acc.getNumberOfOperations();
	}

	/**
	 * Retorna o n�mero de opera��es de cr�dito em uma conta
	 * @param accId identificador da conta
	 * @return Quantidade de opera��es de cr�dito
	 */
	public int getNumberOfCreditOperationsReport(String accId) {
		Account acc = this.findAccount(accId);

		return acc.getNumberOfCreditOperationsReport();
	}

	/**
	 * Retorna o n�mero de opera��es de d�bito em uma conta
	 * @param accId identificador da conta
	 * @return Quantidade de opera��es de d�bito
	 */
	public int getNumberOfDebtOperationsReport(String accId) {
		Account acc = this.findAccount(accId);

		return acc.getNumberOfDebtOperationsReport();
	}

	/**
	 * Gera um relatorio contendo todas as informacoes em uma conta com base em uma data inicial, final e um tipo de operacao
	 * @param begin data in�cio
	 * @param end data fim
	 * @param operationType tipo de opera��o
	 * @param idAccount id da conta
	 * @return Relat�rio
	 * @throws AccountException
	 */
	public ReportManager generateReport(Date begin, Date end,
			String operationType, String idAccount) throws AccountException {

		Account acc = this.findAccount(idAccount);
		
		if (acc == null) {
			return null;
			//throw new AccountException("Account Id Invalid");
		}

		if ( (begin == null) && (end == null) ) {
			return acc.generateReport(operationType);
		} else if (begin == null) {
					return acc.generateReportBefore(end ,operationType);
				} else {
					if (end == null) {
						return acc.generateReportAfter(begin ,operationType);
					} else {
						return acc.generateReport(begin, end ,operationType);
					}
				}
	}

	/**
	 * Retorna o id da conta default
	 * @return ID da conta padr�o
	 */
	public String getDefaultAccountId() {

		return defaultAccount.getId();
	}

	/**
	 * Retorna o n�mero de opera��es de cr�dito
	 * @return Quantidade de opera��es de cr�dito
	 */
	public int getNumberOfCreditOperationsReport() {

		int result = 0;
		Iterator<Account> itAcc = accounts.iterator();
		
		while (itAcc.hasNext()) {
			result += itAcc.next().getNumberOfCreditOperationsReport();
		}
		
		return result + defaultAccount.getNumberOfCreditOperationsReport();
	}
	
	/**
	 * Retorna o n�mero de opera��es de d�bito
	 * @return Quantidade de opera��es de d�bito
	 */
	public int getNumberOfDebtOperationsReport() {

		int result = 0;
		Iterator<Account> itAcc = accounts.iterator();
		
		while (itAcc.hasNext()) {
			result += itAcc.next().getNumberOfDebtOperationsReport();
		}
		
		return result + defaultAccount.getNumberOfDebtOperationsReport();
	}

	public Account getAccount(String id) {
		
		for (Iterator<Account> iterator = this.accounts.iterator(); iterator.hasNext();) {
			Account account = iterator.next();
			if(account.getId().equals(id)){
				return account;
			}
		}
		
		return null;
	}
	
}
